#include "ui/colorpicker.h"
#include "ui/helpers.h"
#include "context.h"
#include "texture.h"

using namespace anip;

ui::Slider* ui::RectangularColorPicker::_makeSlider(
    const TrivariateColorProvider::ComponentInfo& info)
{
    Slider* s = nullptr;
    FlowHelper::beginRow((Container&)*this, info.name);
    s = FlowHelper::slider((Container&)*this, info.min, info.max, 0);
    if (info.lbound && info.rbound)   s->boundaryBehavior = Slider::Boundary::AllowNeither;
    if (info.lbound && !info.rbound)  s->boundaryBehavior = Slider::Boundary::AllowOverflow;
    if (!info.lbound && info.rbound)  s->boundaryBehavior = Slider::Boundary::AllowUnderflow;
    if (!info.lbound && !info.rbound) s->boundaryBehavior = Slider::Boundary::AllowBoth;
    if (info.warp) s->canWarp = true;
    FlowHelper::endRow((Container&)*this);
    return s;
}

ui::RectangularColorPicker::RectangularColorPicker(
    Container* parent, invec2 pos, invec2 size, 
    const TrivariateColorProvider* provider, const TrueColor& col)
    : ColorPickerBase(parent, pos, size), _provider(provider)
{
    _color = col;
    _flowColFac = 0.1f;

    _panel = new Draggable<Panel>((Container*)this, vec2{ 10, 10 }, vec2{ _size.x - 20 });
    FlowHelper::endRow((Container&)*this);

    _panel->layoutOptions.type = LayoutOptions::Flow;
    _panel->onMouseButton = [&](Control& self, MouseButton btn, ButtonAction act)
    {
        if (act == ButtonAction::Down)
        {
            vec2 pos = _panel->toLocalPos(Context::mousePos()) / _panel->size();
            _handleSelection(pos);
            if (btn == MouseButton::Right)
                _showRange = false;
        }
        if (act == ButtonAction::Up && btn == MouseButton::Right)
        {
            _showRange = true;
            postSignal({ Signal::Type::NeedsRedraw });
        }
    };
    _panel->onBeginDrag = [&](Draggable<Panel>& self, MouseButton btn, invec2 pos)
    {
        vec2 p = pos / _panel->size();
        _handleSelection(p);
        if (btn == MouseButton::Right)
            _showRange = false;
        return true;
    };
    _panel->onDrag = [&](Draggable<Panel>& self, invec2 start, invec2 pos)
    {
        vec2 p = pos / _panel->size();
        _handleSelection(p);
    };
    _panel->onEndDrag = [&](Draggable<Panel>& self)
    {
        _showRange = true;
        postSignal({ Signal::Type::NeedsRedraw });
    };

    _preview = FlowHelper::button((Container&)*this, L"");
    FlowHelper::endRow((Container&)*this);

    _preview->layoutOptions.stretchX = 1;
    _preview->layoutOptions.minSize.y = 25;

    _s1 = _makeSlider(provider->first());
    _s2 = _makeSlider(provider->second());
    _s3 = _makeSlider(provider->third());

    auto onSliderChanged = [&](Slider& self)
    {
        float vals[4]{ _s1->value(), _s2->value(), _s3->value(), 1.0f };
        _provider->toTrueColor(vals, &_color);
        if (onColorChanged) onColorChanged(*this);
        _updateControls(true);
        postSignal({ Signal::Type::NeedsRedraw });
    };

    _s1->onValueChanged = onSliderChanged;
    _s2->onValueChanged = onSliderChanged;
    _s3->onValueChanged = onSliderChanged;

    _updateControls();
}

void ui::RectangularColorPicker::_updateControls(bool onlyPreview)
{
    postSignal({ Signal::Type::NeedsRedraw });
    color srgb;
    TrueColor::to_sRGB(&_color, &srgb, 1);
    _preview->normalColor = srgb;

    if (onlyPreview) return;

    float vals[4]{};
    _provider->fromTrueColor(&_color, vals, 1);
    _s1->setValue(vals[0]);
    _s2->setValue(vals[1]);
    _s3->setValue(vals[2]);
}

void ui::RectangularColorPicker::_updateTexture()
{
    int sx = ((int)(_size.x) - 20) / 2;
    int sy = sx;

    if (sx < 10 || sy < 10) return;
    if (_tex == nullptr)
    {
        TextureOptions options;
        options.format = ColorFormat::RGBA;
        options.precision = PrecisionType::Float32;
        _tex = new Texture2D(sx, sy, &options);
    }
    else if (_tex->size() == vec2{ sx, sy } && _tex_v1 == _s1->value())
        return;

    _tex_v1 = _s1->value();
    float* buffer = new float[sx * sy * 4];
    auto 
        info2 = _provider->second(),
        info3 = _provider->third();

    int i = 0;
    for (int y = 0; y < sy; y++)
        for (int x = 0; x < sx; x++)
        {
            buffer[i++] = _tex_v1;
            buffer[i++] = (float)x / sx * (info2.max - info2.min) + info2.min;
            buffer[i++] = (1 - (float)y / sy) * (info3.max - info3.min) + info3.min;
            buffer[i++] = 1;
        }

    _provider->toTrueColor(buffer, (TrueColor*)buffer, sx * sy);
    TrueColor::to_sRGB((TrueColor*)buffer, (color*)buffer, sx * sy);
    _tex->setData(sx, sy, (void*)buffer);
    delete[] buffer;
}

void ui::RectangularColorPicker::_handleSelection(vec2 pos)
{
    auto
        info2 = _provider->second(),
        info3 = _provider->third();

    float vals[4]{ _s1->value(),
        pos.x * (info2.max - info2.min) + info2.min,
        (1 - pos.y) * (info3.max - info3.min) + info3.min, 1 };
    _provider->toTrueColor(vals, &_color);
    _s2->setValue(vals[1]);
    _s3->setValue(vals[2]);
    if (onColorChanged) onColorChanged(*this);
    _updateControls(true);
}

void ui::RectangularColorPicker::setColor(const TrueColor& c)
{
    postSignal({ Signal::Type::NeedsRedraw });
    _color = c;
    _updateControls();
}

void ui::RectangularColorPicker::paint()
{
    _updateTexture();
    paintChildren();

    auto
        info2 = _provider->second(),
        info3 = _provider->third();

    if (_tex != nullptr) Draw::textureHDR(
        _panel->toAbsolutePos(), _panel->size(), *_tex, Colors::white, _showRange);

    vec2 cursor{
        (_s2->value() - info2.min) / (info2.max - info2.min),
        1 - (_s3->value() - info3.min) / (info3.max - info3.min) };
    vec2 cpos = _panel->toAbsolutePos(_panel->size() * cursor);
    Draw::ellipse(cpos - vec2{ 5, 5 }, vec2{ 10, 10 }, Colors::white);
    Draw::ellipseOutline(cpos - vec2{ 5, 5 }, vec2{ 10, 10 }, 1.0f, Colors::black);
}

void ui::RectangularColorPicker::flow(Flow& f)
{
    postSignal({ Signal::Type::NeedsRedraw });

    if (layoutOptions.stretchX > 0)
        _size.x = (f.borderRight - f.currentX) * layoutOptions.stretchX;

    _panel->setSize(vec2{ _size.x - 20 });
    
    if (layoutOptions.type == LayoutOptions::Flow)
    {
        flowChildrenDefault();
        _size.y = _s3->position().y + _s3->size().y + 10;
        defaultFlowLayout(f);
    }
    else
        Control::flow(f);
}